/*
 * @Author: wangdazhuang
 * @Date: 2024-07-18 19:14:37
 * @LastEditTime: 2025-01-31 13:53:35
 * @LastEditors: wangdazhuang
 * @Description: 
 * @FilePath: /pornhub_app/lib/http/http_service.dart
 */
import 'package:dio/dio.dart';
import 'package:pornhub_app/components/easy_toast.dart';
import 'package:pornhub_app/http/api_code.dart';
import 'package:pornhub_app/http/base_repsponse_model.dart';
import 'package:pornhub_app/env/environment_service.dart';
import 'package:pornhub_app/http/http_error_interceptor.dart';
import 'package:pornhub_app/http/http_resp_interceptor.dart';
import 'package:pornhub_app/utils/logger.dart';
import 'package:flutter/foundation.dart';
// ignore: implementation_imports
import 'package:dio/src/multipart_file.dart' as ddd;
// ignore: implementation_imports
import 'package:dio/src/form_data.dart' as ddd;
import '../model/image_upload_resp_model.dart';
import 'http_const.dart';
import 'http_exception_handler.dart';
import 'http_req_interceptor.dart';
import 'http_resp_convert.dart';

typedef JSON2ModelComplete<T> = T Function(Map<String, dynamic> json);

class Http {
  static late Dio _dio;
  static final Http _singleton = Http._internal();
  factory Http() => _singleton;

  ///初始化
  Http._internal() {
    _dio = Dio(
      BaseOptions(
        baseUrl: Environment.kbaseAPI,
        connectTimeout: const Duration(seconds: 60),
        sendTimeout: !kIsWeb ? const Duration(seconds: 30) : null, // web不支持
        receiveTimeout: const Duration(seconds: 60),
        contentType: HttpConst.kContentTypeApplicationJSON,
      ),
    );

    //添加请求和相应拦截器
    _dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: HttpReqInterceptor.onRequest,
        onResponse: HttpRespInterceptor.onResponse,
        onError: HttpErrorInterceptor.onError,
      ),
    );
  }

  ///get
  /// requestEntireModel针对 {total:123,data:[]} 的这种情况
  Future get<T>({
    required String url,
    Map<String, dynamic>? queryMap,
    JSON2ModelComplete<T>? complete,
    bool? requestEntireModel,
    CancelToken? token,
  }) async {
    return _request(
      method: "GET",
      url: url,
      queryMap: queryMap,
      requestEntireModel: requestEntireModel,
      complete: complete,
      token: token,
    );
  }

  /// post
  /// requestEntireModel针对 {total:123,data:[]} 的这种情况
  Future post<T>(
      {required String url,
      Map<String, dynamic>? body,
      JSON2ModelComplete<T>? complete,
      bool? requestEntireModel,
      bool? isFile}) async {
    return _request(
        method: "POST",
        url: url,
        body: body,
        requestEntireModel: requestEntireModel,
        complete: complete,
        isFile: isFile);
  }

  //图片上传
  Future<ImageUploadRspModel?> uploadImage(
      Uint8List bytes, void Function(int, int) progress) async {
    final fromFile = ddd.MultipartFile.fromBytes(bytes,
        filename: '#${DateTime.now().millisecondsSinceEpoch}.png');
    final formData = ddd.FormData.fromMap({'file': fromFile});

    _dio.options.headers.update(Headers.contentTypeHeader,
        (v) => HttpConst.kMutiplyPartFileContentType);
    _dio.options.baseUrl = Environment.kbaseAPI.replaceFirst("api", "file");
    try {
      Response r = await _dio.post(
        'upload/multipart/img',
        data: formData,
        onSendProgress: (count, total) {
          progress(count, total);
        },
      );
      if (r.data is BaseRespModel<dynamic>) {
        BaseRespModel respmodel = r.data;
        logger.d("uploadImage result >>> ${respmodel.data} ");
        final model = ImageUploadRspModel.fromJson(
            Map<String, dynamic>.from(respmodel.data));
        return model;
      }
      return null;
    } on DioException catch (e) {
      logger.d(e.toString());
      rethrow;
    }
  }

  /// 多表单文件和json post上传 一般是单张图片 + post参数
  Future<dynamic> multiPartFormPost({
    required String url,
    required Uint8List file,
    Map<String, dynamic>? body,

    /// 用来取消请求的 ，这边分片上传会出现多个请求，退出页面需要取消全部请求，
    CancelToken? token,

    /// 是否是视频分片上传
    bool? isVideo,

    /// 进度百分比回掉
    void Function(int, int)? progress,
  }) async {
    final isVideossss = isVideo == true;
    final millseconds = DateTime.now().millisecondsSinceEpoch;
    final filename = "$millseconds.${isVideossss ? 'mov' : 'png'}";
    final fromFile = ddd.MultipartFile.fromBytes(file, filename: filename);
    removeNull(body);
    final formData = ddd.FormData.fromMap({'file': fromFile, ...?body});
    _dio.options.headers.update(Headers.contentTypeHeader,
        (v) => HttpConst.kMutiplyFormPostContentType);
    if (isVideossss) {
      _dio.options.baseUrl = Environment.kbaseAPI.replaceFirst("api", "file");
    }
    try {
      Response r = await _dio.post(
        url,
        data: formData,
        cancelToken: token,
        onSendProgress: (count, total) {
          if (progress != null) {
            progress(count, total);
          }
        },
      );
      // 和普通请求保持统一，抛出异常表示错误
      if (r.data == null) {
        throw {"code": 0, "msg": 'dio empty data'};
      }
      BaseRespModel rr = r.data;
      logger.d(
          'multiPartFormPost $url>>$body>>file:${file.length}>>{code:${rr.code},data:${rr.data},msg:${rr.msg}}');
      if (rr.code != ApiCode.ok) {
        if (rr.msg?.isNotEmpty == true) {
          EasyToast.show(rr.msg!);
        }
        throw {"code": rr.code ?? 0, "msg": rr.msg};
      }
      return rr.data;
    } on DioException catch (e) {
      logger.d('multiPartFormPost $url>>$body>>file:${file.length}>>$e');
      rethrow;
    }
  }

  ///筛选空值情况
  Map? removeNull(Map? m) {
    if (m == null) return null;
    final keysToRemove = [];
    m.forEach((k, v) {
      if (v == null) {
        keysToRemove.add(k);
      } else if (v is Map) {
        removeNull(v);
      } else if (v is List) {
        for (final e in v) {
          if (e is Map) {
            removeNull(e);
          }
        }
      }
    });
    for (final k in keysToRemove) {
      m.remove(k);
    }
    return m;
  }

  ////request
  Future _request<T>(
      {required String method,
      required String url,
      Map<String, dynamic>? body,
      Map<String, dynamic>? queryMap,
      JSON2ModelComplete<T>? complete,
      bool? requestEntireModel,
      CancelToken? token,
      bool? isFile}) async {
    if (isFile == true) {
      _dio.options.baseUrl = Environment.kbaseAPI.replaceFirst("api", "file");

      ///替换请求头
      _dio.options.headers.update(Headers.contentTypeHeader,
          (v) => HttpConst.kContentTypeApplicationJSON);
    } else {
      final currentDioHasReplaceHeader =
          _dio.options.baseUrl.endsWith("file/") ||
              _dio.options.headers.values
                  .contains(HttpConst.kMutiplyFormPostContentType);
      if (currentDioHasReplaceHeader) {
        ///替换请求头
        _dio.options.headers.update(Headers.contentTypeHeader,
            (v) => HttpConst.kContentTypeApplicationJSON);
      }
      _dio.options.baseUrl = Environment.kbaseAPI.replaceFirst("file", "api");
    }
    try {
      Response resp = await _dio.request(url,
          cancelToken: token ?? CancelToken(),
          data: removeNull(body),
          queryParameters: removeNull(queryMap) as Map<String, dynamic>?,
          options: Options(method: method));
      final finalData = await HttpRespConvert.convertRsp<T>(
          resp, complete, requestEntireModel,
          url: url, method: method, queryMap: queryMap, body: body);
      return finalData;
    } on DioException catch (e) {
      HttpExceptionHandler.handleException(e);
    }
  }
}

Http httpInstance = Http();
